//
// Created by hjie on 24-1-9.
//

#include "xstack_filter_format.h"
#include "base_util.h"
#include "codec_config.h"
#include "input_output_context.h"
#include "gtest/gtest.h"
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <libavcodec/packet.h>
#include <libavfilter/avfilter.h>
#include <libavfilter/buffersink.h>
#include <libavformat/avformat.h>
#include <libavutil/rational.h>
#include <string>
#include <memory>
#include <strings.h>
#include <vector>

static std::string SelectColor()
{
    srand(time(nullptr) + rand() % 2000);
    srand(rand() % 10000 + rand() % 999);
    static std::vector<std::string> color_list = {"orange","red","yellow", "blue","black@1.0","white", "white","gray","gray"};
    return color_list[rand() % color_list.size()];
}

void ComputePositionData(XstackFilterDataInfo & xstack_data)
{
    int width  = xstack_data.m_config.m_width;
    int height = xstack_data.m_config.m_height;
    int lenght = (int)xstack_data.m_input_file_map.size();
    int cols = floor(sqrt(lenght));
    int rows = ceil((double)lenght/cols);
    int invq = rows;
    int cell_width  = (width/invq);
    int cell_height = (height/cols);  //((double)height/width) * (cell_width);
    printf("cols:%d rows:%d cell_width:%d cell_height:%d.\n", cols, rows, cell_width, cell_height);
    int width_block  = (width  - (cell_width  * rows)) / 2;
    int height_block = (height - (cell_height * cols)) / 2;
    printf("wblock:%d\thblock:%d.\n", width_block, height_block);
    struct CellData
    {
        int x;
        int y;
        int cell_width;
        int cell_height;
    };
    int floor_num = (cols * rows) > lenght ? cols -1 : cols;
    int x = width_block;
    int y = height_block;
    std::vector<CellData> cell_data_vec;
    for (int i = 0; i < floor_num; i++)
    {
        x = width_block;
        for (int j = 0; j < rows; j++)
        {
            CellData data;
            data.x = x;
            data.y = y;
            data.cell_height = cell_height;
            data.cell_width  = cell_width;
            x += cell_width;
            cell_data_vec.push_back(data);
        }
        y += cell_height;
    }
    int num = lenght - (floor_num * rows);
    x = (width - (num * cell_width))/2;

    for (int i = 0; i < num; i++)
    {
        CellData data;
        data.x = x;
        data.y = y;
        data.cell_width = cell_width;
        data.cell_height= cell_height;
        cell_data_vec.push_back(data);
        x += cell_width;
    }
    char buffer[1024];
    std::memset(buffer,0,sizeof(buffer));
    lenght = 0;
    int number = 0;
    for (auto & data : cell_data_vec)
    {
        printf("x:%d y:%d w:%d h:%d.\n", data.x, data.y, data.cell_width, data.cell_height);

        xstack_data.m_input_file_map[number]->m_x = data.x;
        xstack_data.m_input_file_map[number]->m_y = data.y;
        lenght += sprintf(buffer + lenght, "%d_%d", data.x, data.y);
        number++;
        if (number != xstack_data.m_input_file_map.size())
        {
            lenght += sprintf(buffer + lenght, "|");
        }        
    }
    xstack_data.m_config.m_content = buffer;
    xstack_data.m_config.m_cell_width  = cell_width;
    xstack_data.m_config.m_cell_height = cell_height;
}

void ProcessInputStream(XstackFilterDataInfo & xstack_data, InputFileAndDataInfo * input_ctx)
{
    static int index = 0;
    auto input_ctx_ptr = std::shared_ptr<InputFileAndDataInfo>(input_ctx);
    auto video_stream = input_ctx->m_input_file_ctx->VideoMediaStream(); 
    auto input_stream = std::make_shared<InputStream>();
    input_stream->m_media_stream = video_stream;
    input_stream->m_stream_link_input_file_ctx = input_ctx_ptr;
    input_stream->m_decode_frame_queue.InitMessageQueue(200);
    input_stream->m_now_time = 0;
    input_stream->m_duration_of_frame = (double)video_stream->AvStream()->avg_frame_rate.den/video_stream->AvStream()->avg_frame_rate.num * AV_TIME_BASE;
    // will change that
    //int index = input_ctx->m_index;
    xstack_data.m_input_stream_map[index++] = input_stream;

    printf("frame rate : %d/%d.\n", video_stream->AvStream()->avg_frame_rate.num, video_stream->AvStream()->avg_frame_rate.num);
    printf("stream frame rate : %d/%d.\n", video_stream->AvStream()->time_base.num, video_stream->AvStream()->time_base.den);
    printf("duration of frame : %ld.\n", input_stream->m_duration_of_frame);

    /* auto audio_stream = input_ctx->m_input_file_ctx->AudioMediaStream(); */
    /* auto audio_input_stream = std::make_shared<InputStream>(); */
    /* audio_input_stream->m_media_stream = audio_stream; */
    /* xstack_data.m_input_stream_map[index] = audio_input_stream; */
}

void DumpInputData(XstackFilterDataInfo &xstack_data)
{
    for (auto & data : xstack_data.m_input_file_map)
    {
    	printf("input file name : %s.\n", data.second->m_input_file_ctx->AvFormatContext()->url);
    	printf("video decode name : %s.\n", data.second->m_input_file_ctx->VideoDecoder()->AvCodecCtx()->codec->name);
    	auto codec_ctx = data.second->m_input_file_ctx->VideoDecoder()->AvCodecCtx();
	    printf("frame rate : %d/%d.\n", codec_ctx->framerate.num, codec_ctx->framerate.den);
    	printf("timebase : %d/%d.\n", codec_ctx->pkt_timebase.den, codec_ctx->pkt_timebase.den);	
    }
}

bool InitInputFile(std::vector<std::string> input_file_vec, XstackFilterDataInfo & xstack_data)
{
    int index = 0;
    for (auto & data : input_file_vec)
    {
        std::string input_name = data;
        auto * input_data = new InputFileAndDataInfo();
        input_data->m_input_file_ctx = new InputFormatContext();
        bool result = input_data->m_input_file_ctx->OpenUrlWithFindStreamInfo(input_name);
        if (result)
        {
            CodecConfig codec_config;
	        if(!input_data->m_input_file_ctx->CreateVideoDecoderCtx("", codec_config))
	        {
		        printf("create video decode failed.\n");
            }
            printf("index : %d filename : %s.\n", index, input_name.c_str());
            input_data->m_read_packet_queue.InitMessageQueue(200);
            input_data->m_index  = index;
            input_data->m_is_eof = false;
            ProcessInputStream(xstack_data,input_data);
            xstack_data.m_input_file_map[index] = input_data;
            index++;
        }
        else
        {
            delete input_data;
            printf("open file failed : %s.\n", input_name.c_str());
        }
    }
    return true;
}

bool InitXstackFilter(XstackFilterDataInfo & xstack_data)
{
    const AVFilter * xstack_filter = avfilter_get_by_name("xstack");
    const AVFilter * buffer_filter = avfilter_get_by_name("buffer"); 
    const AVFilter * buffersink_filter = avfilter_get_by_name("buffersink");
    const AVFilter * zscale_filter = avfilter_get_by_name("zscale");
    const AVFilter * format_filter = avfilter_get_by_name("format");
    const AVFilter * frame_filter  = avfilter_get_by_name("framerate");
    const AVFilter * nullsrc_filter= avfilter_get_by_name("nullsrc");
    const AVFilter * overlay_filyer=avfilter_get_by_name("overlay");
    if (xstack_filter && buffer_filter && buffersink_filter && zscale_filter)
    {
        xstack_data.m_config.m_width  = 1280;
        xstack_data.m_config.m_height = 720;
		xstack_data.m_filter_graph = avfilter_graph_alloc();
		printf("find xstack buffer buffer_sink zscale filter.\n");
		int xstack_input_number = 0;
		AVFilterContext * xstack_filter_ctx = nullptr;
        char xstack_buffer_sink[1024] = {0};
        memset(xstack_buffer_sink,0,sizeof(xstack_buffer_sink));
        ComputePositionData(xstack_data);
        printf("xstack filter arsg : %s.\n", xstack_buffer_sink);
        sprintf(xstack_buffer_sink, "inputs=%ld:layout=%s", xstack_data.m_input_file_map.size(), xstack_data.m_config.m_content.c_str());
        avfilter_graph_create_filter(&xstack_filter_ctx,xstack_filter,"xstack_input",xstack_buffer_sink,nullptr,xstack_data.m_filter_graph);
		for (auto & data : xstack_data.m_input_stream_map)
		{
		    AVFilterContext * buffer_filter_ctx = nullptr;
		    char filter_ctx_name[10];
		    memset(filter_ctx_name, 0, sizeof(filter_ctx_name));
		    snprintf(filter_ctx_name, sizeof(filter_ctx_name), "input_%d", xstack_input_number);
		    char args[120];
		    memset(&args, 0, sizeof(args));
		    auto video_stream = data.second->m_media_stream->AvStream();
		    snprintf(args,sizeof(args),"width=%d:height=%d:pix_fmt=%d:time_base=%d/%d:frame_rate=%d/%d:sar=%d/%d",
			    video_stream->codecpar->width,video_stream->codecpar->height,
			    video_stream->codecpar->format,
			    video_stream->time_base.num,
			    video_stream->time_base.den,
			    video_stream->avg_frame_rate.num,
			    video_stream->avg_frame_rate.den,
                video_stream->sample_aspect_ratio.num, video_stream->sample_aspect_ratio.den);

		    int result = avfilter_graph_create_filter(&buffer_filter_ctx, buffer_filter, filter_ctx_name, args, nullptr, xstack_data.m_filter_graph);
		    if (result < 0)
		    {
				PrintLog(result);
		    }
		    else 
		    {
                AVFilterContext * nullsrc_ctx = nullptr;
                std::string name = "input_" + std::to_string(xstack_input_number);
                char args[100];
                memset(args, 0, sizeof(args));
                snprintf(args,sizeof(args) - 1,"s=%dx%d:r=25/1:d=%ld",
                        xstack_data.m_config.m_cell_width, 
                        xstack_data.m_config.m_cell_height,
                        long(video_stream->duration * av_q2d(video_stream->time_base)));
                printf("%s.\n", args);
                avfilter_graph_create_filter(&nullsrc_ctx, nullsrc_filter, name.c_str(), args, nullptr, xstack_data.m_filter_graph);
                
                name = "drawbox_" + std::to_string(xstack_input_number);
                AVFilterContext * drawbox_ctx = nullptr;
                
                memset(args, 0, sizeof(args));
                std::string color = SelectColor();
                snprintf(args, sizeof(args),"x=%d:y=%d:w=%d:h=%d:color=%s:t=fill",
                        0,0,
                        xstack_data.m_config.m_cell_width,
                        xstack_data.m_config.m_cell_height,
                        color.c_str());
                printf("%s.\n", args);
                avfilter_graph_create_filter(&drawbox_ctx, avfilter_get_by_name("drawbox"), name.c_str(), args, nullptr, xstack_data.m_filter_graph);

                result = avfilter_link(nullsrc_ctx, 0, drawbox_ctx, 0);
                if (result < 0)
                {
                    PrintLog(result);
                }

                name = "overlay_" + std::to_string(xstack_input_number);
                memset(args, 0, sizeof(args));
                int x = (xstack_data.m_config.m_cell_width - video_stream->codecpar->width) / 2;
                int y = (xstack_data.m_config.m_cell_height - video_stream->codecpar->height)/2;
                 if (video_stream->codecpar->width  > xstack_data.m_config.m_cell_width ||
                    video_stream->codecpar->height > xstack_data.m_config.m_cell_height)
                {
                    x = 0;
                    y = 0; 
                }
                snprintf(args, sizeof(args),"format=yuv420:x=%d:y=%d",x,y); 
                AVFilterContext * overlay_ctx = nullptr;
                avfilter_graph_create_filter(&overlay_ctx, overlay_filyer, name.c_str(), args, nullptr, xstack_data.m_filter_graph);
                avfilter_link(drawbox_ctx, 0, overlay_ctx, 0);
                if (video_stream->codecpar->width  > xstack_data.m_config.m_cell_width ||
                    video_stream->codecpar->height > xstack_data.m_config.m_cell_height)
                {
                    name = "zscale_" + std::to_string(xstack_input_number);
                    AVFilterContext * zscale_ctx = nullptr;
                    memset(args, 0, sizeof(args)); 
                    sprintf(args, "s=%dx%d", xstack_data.m_config.m_cell_width, xstack_data.m_config.m_cell_height);
                    printf("%s.\n", args);
                    avfilter_graph_create_filter(&zscale_ctx, zscale_filter, name.c_str(), args, nullptr, xstack_data.m_filter_graph);
                    avfilter_link(buffer_filter_ctx, 0, zscale_ctx, 0);
                    avfilter_link(zscale_ctx, 0, overlay_ctx, 1);
                }
                else 
                {
                    result = avfilter_link(buffer_filter_ctx, 0, overlay_ctx, 1);
                    if (result < 0)
                    {
                        PrintLog(result);
                    }
                }
				result = avfilter_link(overlay_ctx, 0, xstack_filter_ctx, xstack_input_number++);
				if (result < 0)
				{
				    PrintLog(result);
				}
                data.second->m_filter_buffer = buffer_filter_ctx;
		    }
		}
        AVFilterContext * format = nullptr;
        avfilter_graph_create_filter(&format, format_filter, "format1", "pix_fmts=yuv420p", nullptr, xstack_data.m_filter_graph);
        avfilter_link(xstack_filter_ctx, 0, format, 0);
        AVFilterContext * buffer_sink_ctx = nullptr;
		int result = avfilter_graph_create_filter(&buffer_sink_ctx, buffersink_filter, "out", nullptr, nullptr, xstack_data.m_filter_graph);
		if (result < 0)
		{
		    PrintLog(result);
		}
        AVFilterContext * framerate_ctx = nullptr;
        avfilter_graph_create_filter(&framerate_ctx, frame_filter, "framerate", "fps=25", nullptr, xstack_data.m_filter_graph);
        avfilter_link(format, 0, framerate_ctx, 0);
		result = avfilter_link(framerate_ctx, 0, buffer_sink_ctx, 0);
		if (result < 0)
		{
		    PrintLog(result);
		    return false;
		}
        av_opt_set(xstack_data.m_filter_graph, "threads", "6", AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM);
        result = avfilter_graph_config(xstack_data.m_filter_graph, nullptr);
		if (result < 0)
		{
		    PrintLog(result);
		}
		char * ptr = avfilter_graph_dump(xstack_data.m_filter_graph, nullptr);
		if (ptr)
		{
		    printf("%s.\n", ptr);
		    av_free(ptr);
		}
        xstack_data.m_output_file_ctx = std::make_shared<OutputFileAndDataInfo>();
        xstack_data.m_output_file_ctx->m_output_is_init = true;
        xstack_data.m_filter_frame_queue.InitMessageQueue(200);
        xstack_data.m_output_file_ctx->m_filter_sink_ctx = buffer_sink_ctx;
        xstack_data.m_output_file_ctx->m_output_file_ctx = nullptr;
		return true;
    }
    return false;
}

bool InitOutputFile(const std::string & m_output_filename, XstackFilterDataInfo & xstack_filter_data)
{
   return false; 
}

void ProcessLibX264(XstackFilterDataInfo & xstack_filter_data, AVFrame * frame)
{
    if (!xstack_filter_data.m_output_file_ctx->m_output_file_ctx)
    {
        xstack_filter_data.m_output_file_ctx->m_output_file_ctx = new OutputFormatContext();
        xstack_filter_data.m_output_file_ctx->m_output_file_ctx->CreateUrl("./test.mp4");
    }
    /* xstack_filter_data.m_output_file_ctx->m_output_file_ctx = new OutputFormatContext(); */
    /* xstack_filter_data.m_output_file_ctx->m_output_file_ctx->CreateUrl("./test.flv"); */
    CodecConfig codec_config;
    codec_config.m_video_config.m_height  = xstack_filter_data.m_config.m_height;
    codec_config.m_video_config.m_width   = xstack_filter_data.m_config.m_width;
    codec_config.m_video_config.m_frame   = frame;
    // cfr bitrate control cfr value is 23
    codec_config.m_video_config.m_config_callback = [&xstack_filter_data](AVCodecContext * codec_ctx){

        codec_ctx->width   = av_buffersink_get_w(xstack_filter_data.m_output_file_ctx->m_filter_sink_ctx);
        codec_ctx->height  = av_buffersink_get_h(xstack_filter_data.m_output_file_ctx->m_filter_sink_ctx);
        codec_ctx->sample_aspect_ratio = av_buffersink_get_sample_aspect_ratio(xstack_filter_data.m_output_file_ctx->m_filter_sink_ctx);
        //codec_ctx->sample_aspect_ratio.den = codec_ctx->sample_aspect_ratio.num = 1;
        codec_ctx->profile      = FF_PROFILE_H264_HIGH;
        //codec_ctx->time_base    = av_inv_q(av_buffersink_get_frame_rate(xstack_filter_data.m_output_file_ctx->m_filter_sink_ctx));
        codec_ctx->time_base = av_buffersink_get_time_base(xstack_filter_data.m_output_file_ctx->m_filter_sink_ctx);
        codec_ctx->framerate    = av_buffersink_get_frame_rate(xstack_filter_data.m_output_file_ctx->m_filter_sink_ctx);
        codec_ctx->pix_fmt      = (AVPixelFormat)av_buffersink_get_format(xstack_filter_data.m_output_file_ctx->m_filter_sink_ctx);
        codec_ctx->level        = 31;
        codec_ctx->bit_rate     = 0;
        printf("timebase : %d/%d \t frame : %d/%d and w:%d h:%d.\n", 
                codec_ctx->time_base.num, 
                codec_ctx->time_base.den, 
                codec_ctx->framerate.num, 
                codec_ctx->framerate.den,
                codec_ctx->width,
                codec_ctx->height);
    };
    xstack_filter_data.m_output_file_ctx->m_output_file_ctx->CreateVideoStream(xstack_filter_data.m_input_file_map[0]->m_input_file_ctx->VideoMediaStream(), codec_config);
    xstack_filter_data.m_output_file_ctx->m_video_init = true;
    xstack_filter_data.m_output_file_ctx->m_encoder_packet_queue.InitMessageQueue(300);
    /* xstack_filter_data.m_output_file_ctx->m_output_file_ctx->WriterHeaderTailer(); */
    PrintLogMsg("init video stream and encoder");
    if (xstack_filter_data.m_output_file_ctx->m_audio_init)
    {
        xstack_filter_data.m_output_file_ctx->m_output_file_ctx->WriterHeaderTailer();
    }
    else
    {
        xstack_filter_data.m_output_file_ctx->m_sync_process_packet_queue.InitMessageQueue(200);
    }
}


void InitAudioStreamCopyModol(XstackFilterDataInfo & xstack_data)
{
    if (!xstack_data.m_output_file_ctx->m_output_file_ctx)
    {
        xstack_data.m_output_file_ctx->m_output_file_ctx = new OutputFormatContext();
        std::map<std::string, std::string> key_value;
        key_value["major_brand"] = "isom";
        key_value["minor_version"] = "512";
        key_value["compatible_brands"] = "isomiso2avc1mp41";
        xstack_data.m_output_file_ctx->m_output_file_ctx->CreateUrl("./test.mp4");
        xstack_data.m_output_file_ctx->m_output_file_ctx->AddMetaDataToContext(key_value);
    }
    PrintLogMsg("init audio stream and encoder");
    int index = rand() % xstack_data.m_input_file_map.size();
    auto input_file_hdl = xstack_data.m_input_file_map[index];
    auto audio_stream = input_file_hdl->m_input_file_ctx->AudioMediaStream();
    CodecConfig codec;
    xstack_data.m_output_file_ctx->m_index = index;
    codec.m_audio_config.m_copy_decoder_param = true;
    xstack_data.m_output_file_ctx->m_output_file_ctx->CreateAudioStream(audio_stream,codec);    
    xstack_data.m_output_file_ctx->m_audio_init = true;
    if (xstack_data.m_output_file_ctx->m_video_init)
    {
        xstack_data.m_output_file_ctx->m_output_file_ctx->WriterHeaderTailer();
    }
    else
    {
       xstack_data.m_output_file_ctx->m_sync_process_packet_queue.InitMessageQueue(800); 
    }
}


void PushPacketToQueue(XstackFilterDataInfo & xstack_data, AVPacket * packet)
{
    if (xstack_data.m_output_file_ctx->m_audio_init && xstack_data.m_output_file_ctx->m_video_init)
    {
        auto size = xstack_data.m_output_file_ctx->m_sync_process_packet_queue.GetSize();
        if (size > 0)
        {
            while (true)
            {
                AVPacket * pkt = nullptr;
                xstack_data.m_output_file_ctx->m_sync_process_packet_queue.PopPacketNonBlocking(&pkt);
                if (!pkt)
                {
                    break;
                }
                av_interleaved_write_frame(xstack_data.m_output_file_ctx->m_output_file_ctx->AvFormatContext(), pkt);
                av_packet_unref(pkt);
                av_packet_free(&pkt);
            }
        }
        av_interleaved_write_frame(xstack_data.m_output_file_ctx->m_output_file_ctx->AvFormatContext(), packet);
    }
    else
    {
        xstack_data.m_output_file_ctx->m_sync_process_packet_queue.PushPacket(&packet);
    }
}

